package cn.micro.eye.commons.response;

import cn.micro.eye.commons.configuration.MicroProperties;
import cn.micro.eye.commons.configuration.MicroSpringConfiguration;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.annotation.Nullable;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Global Response Body Advice
 *
 * @author lry
 */
@Slf4j
@RestControllerAdvice
public class GlobalResponseBodyAdvice implements BeanFactoryAware, InitializingBean, ResponseBodyAdvice<Object> {

    private Set<String> metaPackages = new HashSet<>();

    @Resource
    private MicroProperties properties;

    @Override
    public void setBeanFactory(@Nullable BeanFactory beanFactory) throws BeansException {
        if (beanFactory == null) {
            return;
        }

        List<String> tempPackages = AutoConfigurationPackages.get(beanFactory);
        if (tempPackages == null || tempPackages.isEmpty()) {
            return;
        }
        metaPackages.addAll(tempPackages);
    }

    @Override
    public void afterPropertiesSet() {
        if (!(properties.getMetaPackages() == null || properties.getMetaPackages().length == 0)) {
            metaPackages.addAll(Arrays.stream(properties.getMetaPackages()).collect(Collectors.toSet()));
        }
        log.info("The meta packages: {}", metaPackages.toString());
    }

    @Override
    public boolean supports(@Nullable MethodParameter methodParameter,
                            @Nullable Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object obj,
                                  @Nullable MethodParameter methodParameter,
                                  @Nullable MediaType mediaType,
                                  @Nullable Class<? extends HttpMessageConverter<?>> converterType,
                                  @Nullable ServerHttpRequest serverHttpRequest,
                                  @Nullable ServerHttpResponse serverHttpResponse) {
        if (methodParameter == null) {
            return obj;
        }

        String methodBeanClassName = methodParameter.getMember().getDeclaringClass().getName();
        for (String metaPackage : metaPackages) {
            if (methodBeanClassName.startsWith(metaPackage)) {
                continue;
            }

            return obj;
        }
        List<String> packages = MicroSpringConfiguration.packages;
        if (packages != null) {
            for (String pg : packages) {
                if (!methodBeanClassName.startsWith(pg)) {
                    return obj;
                }
            }
        }

        // Non Bean and Method Annotation
        NonMeta methodNonMeta = methodParameter.getMethodAnnotation(NonMeta.class);
        NonMeta beanNonMeta = methodParameter.getMember().getDeclaringClass().getAnnotation(NonMeta.class);
        if (methodNonMeta != null || beanNonMeta != null || obj instanceof MetaData) {
            // don't need wrapper metaData data model
            return obj;
        }

        // need wrapper metaData data model
        if (converterType == null || !converterType.isAssignableFrom(StringHttpMessageConverter.class)) {
            return new MetaData(HttpStatus.OK.value(), HttpStatus.OK.getReasonPhrase(), obj);
        }

        // return type is String
        MetaData metaData = new MetaData(HttpStatus.OK.value(), HttpStatus.OK.getReasonPhrase(), obj);
        try {
            return JSON.toJSONString(metaData);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return JSON.toJSONString(metaData);
        }
    }

}
